package besta.moon.sql;

import java.util.List;
import java.sql.*;

import besta.moon.command.CommandCreateDatabase;
import besta.moon.command.CommandSet;
import besta.moon.command.CommandInsert;
import besta.moon.command.CommandUpdate;
import besta.moon.engine.RowSource;
import besta.moon.engine.table.Column;
import besta.moon.engine.table.ForeignKey;
import besta.moon.sql.datatype.Strings;
import besta.moon.sql.expression.function.time.FunctionDayOfYear;
import besta.moon.sql.expression.function.time.FunctionMinute;
import besta.moon.sql.expression.function.time.FunctionYear;
import besta.moon.MoonException;
import besta.moon.MoonConnection;
import besta.moon.MoonCode;
import besta.moon.MoonCommand;
import besta.moon.command.CommandCreateView;
import besta.moon.command.CommandDrop;
import besta.moon.command.CommandTable;
import besta.moon.command.CommandDelete;
import besta.moon.command.CommandSelect;
import besta.moon.engine.Database;
import besta.moon.engine.View;
import besta.moon.engine.index.IndexDescription;
import besta.moon.engine.selector.DataSources;
import besta.moon.engine.selector.multioper.Join;
import besta.moon.engine.selector.multioper.UnionAll;
import besta.moon.engine.selector.result.TableViewResult;
import besta.moon.engine.selector.result.ViewResult;
import besta.moon.sql.datatype.DateTime;
import besta.moon.sql.datatype.Money;
import besta.moon.sql.expression.ExpressionName;
import besta.moon.sql.expression.ExpressionValue;
import besta.moon.sql.expression.Expressions;
import besta.moon.sql.expression.function.FunctionReplace;
import besta.moon.sql.expression.function.numeric.FunctionACos;
import besta.moon.sql.expression.function.numeric.FunctionASin;
import besta.moon.sql.expression.function.numeric.FunctionATan;
import besta.moon.sql.expression.function.numeric.FunctionATan2;
import besta.moon.sql.expression.function.numeric.FunctionAbs;
import besta.moon.sql.expression.function.numeric.FunctionCeiling;
import besta.moon.sql.expression.function.numeric.FunctionCos;
import besta.moon.sql.expression.function.numeric.FunctionCot;
import besta.moon.sql.expression.function.numeric.FunctionDegrees;
import besta.moon.sql.expression.function.numeric.FunctionExp;
import besta.moon.sql.expression.function.numeric.FunctionFloor;
import besta.moon.sql.expression.function.numeric.FunctionLog;
import besta.moon.sql.expression.function.numeric.FunctionLog10;
import besta.moon.sql.expression.function.numeric.FunctionMod;
import besta.moon.sql.expression.function.numeric.FunctionPI;
import besta.moon.sql.expression.function.numeric.FunctionPower;
import besta.moon.sql.expression.function.numeric.FunctionRadians;
import besta.moon.sql.expression.function.numeric.FunctionRand;
import besta.moon.sql.expression.function.numeric.FunctionRound;
import besta.moon.sql.expression.function.numeric.FunctionSign;
import besta.moon.sql.expression.function.numeric.FunctionSin;
import besta.moon.sql.expression.function.numeric.FunctionSqrt;
import besta.moon.sql.expression.function.numeric.FunctionTan;
import besta.moon.sql.expression.function.numeric.FunctionTruncate;
import besta.moon.sql.expression.function.string.FunctionAscii;
import besta.moon.sql.expression.function.string.FunctionBitLen;
import besta.moon.sql.expression.function.string.FunctionChar;
import besta.moon.sql.expression.function.string.FunctionCharLen;
import besta.moon.sql.expression.function.string.FunctionDifference;
import besta.moon.sql.expression.function.string.FunctionInsert;
import besta.moon.sql.expression.function.string.FunctionLCase;
import besta.moon.sql.expression.function.string.FunctionLTrim;
import besta.moon.sql.expression.function.string.FunctionLeft;
import besta.moon.sql.expression.function.string.FunctionLength;
import besta.moon.sql.expression.function.string.FunctionLocate;
import besta.moon.sql.expression.function.string.FunctionOctetLen;
import besta.moon.sql.expression.function.string.FunctionRTrim;
import besta.moon.sql.expression.function.string.FunctionRepeat;
import besta.moon.sql.expression.function.string.FunctionRight;
import besta.moon.sql.expression.function.string.FunctionSoundex;
import besta.moon.sql.expression.function.string.FunctionSpace;
import besta.moon.sql.expression.function.string.FunctionSubstring;
import besta.moon.sql.expression.function.string.FunctionUCase;
import besta.moon.sql.expression.function.system.FunctionCase;
import besta.moon.sql.expression.function.system.FunctionConvert;
import besta.moon.sql.expression.function.system.FunctionIIF;
import besta.moon.sql.expression.function.time.FunctionDayOfMonth;
import besta.moon.sql.expression.function.time.FunctionDayOfWeek;
import besta.moon.sql.expression.function.time.FunctionHour;
import besta.moon.sql.expression.function.time.FunctionMonth;
import besta.moon.sql.expression.function.time.FunctionTimestampAdd;
import besta.moon.sql.expression.function.time.FunctionTimestampDiff;
import besta.moon.sql.expression.ExpressionArithmetic;
import besta.moon.sql.expression.ExpressionInSelect;
import besta.moon.sql.parser.SQLToken;
import besta.moon.sql.parser.SQLTokenizer;
import besta.moon.util.MoonUtils;

public final class MoonParser {

    MoonConnection con;
    private char[] sql;
    private List<SQLToken> tokens;
    private int tokenIdx;

    public MoonCommand parse(MoonConnection con, String sqlString)
            throws SQLException {
        this.con = con;
        MoonCommand cmd = parse(sqlString.toCharArray());
        SQLToken token = nextToken();
        if (token != null) {
            throw createSyntaxError(token, MoonCode.STXADD_ADDITIONAL_TOK);
        }
        return cmd;
    }

    private MoonCommand parse(char[] sql) throws SQLException {
        this.sql = sql;
        this.tokens = SQLTokenizer.parseSQL(sql);
        tokenIdx = 0;

        SQLToken token = nextToken(COMMANDS);
        switch (token.value) {
            case SQLTokenizer.SELECT:
                return select();
            case SQLTokenizer.DELETE:
                return delete();
            case SQLTokenizer.INSERT:
                return insert();
            case SQLTokenizer.UPDATE:
                return update();
            case SQLTokenizer.CREATE:
                return create();
            case SQLTokenizer.DROP:
                return drop();
            case SQLTokenizer.ALTER:
                return alter();
            case SQLTokenizer.SET:
                return set();
            case SQLTokenizer.USE:
                token = nextToken(MISSING_EXPRESSION);
                String name = token.getName(sql);
                checkValidIdentifier(name, token);
                CommandSet set = new CommandSet(con.log, SQLTokenizer.USE);
                set.name = name;
                return set;
            case SQLTokenizer.EXECUTE:
                return execute();
            case SQLTokenizer.TRUNCATE:
                return truncate();
            default:
                throw new Error();
        }
    }

    public Expression parseExpression(String expr) throws SQLException {
        this.sql = expr.toCharArray();
        this.tokens = SQLTokenizer.parseSQL(sql);
        tokenIdx = 0;
        return expression(null, 0);
    }

    private SQLException createSyntaxError(SQLToken token, String addMessageCode) {
        String message = getErrorString(token, addMessageCode, null);
        return MoonException.create(MoonCode.CUSTOM_MESSAGE, message);
    }

    private SQLException createSyntaxError(SQLToken token,
            String addMessageCode, Object param0) {
        String message = getErrorString(token, addMessageCode, param0);
        return MoonException.create(MoonCode.CUSTOM_MESSAGE, message);
    }

    private SQLException createSyntaxError(SQLToken token, int[] validValues) {
        String msgStr = MoonException.translateMsg(MoonCode.STXADD_KEYS_REQUIRED, new Object[]{});

        StringBuffer msgBuf = new StringBuffer(msgStr);

        for (int i = 0; i < validValues.length; i++) {
            String name = SQLTokenizer.getKeyWord(validValues[i]);
            if (name == null) {
                name = String.valueOf((char) validValues[i]);
            }
            msgBuf.append(name);
            if (i < validValues.length - 2) {
                msgBuf.append(", ");
            } else if (i == validValues.length - 2) {
                msgBuf.append(" or ");
            }
        }

        String message = getErrorString(token, MoonCode.CUSTOM_MESSAGE, msgBuf);
        return MoonException.create(MoonCode.CUSTOM_MESSAGE, message);
    }

    private String getErrorString(SQLToken token, String middleMsgCode,
            Object middleMsgParam) {
        StringBuilder buffer = new StringBuilder(1024);

        if (token != null) {
            Object[] params = {String.valueOf(token.offset),
                String.valueOf(sql, token.offset, token.length)};
            String begin = MoonException.translateMsg(MoonCode.SYNTAX_BASE_OFS, params);
            buffer.append(begin);
        } else {
            String begin = MoonException.translateMsg(MoonCode.SYNTAX_BASE_END, new Object[]{});
            buffer.append(begin);
        }

        String middle = MoonException.translateMsg(middleMsgCode,
                new Object[]{middleMsgParam});
        buffer.append(middle);
        int valOffset = (token != null) ? token.offset : sql.length;
        int valBegin = Math.max(0, valOffset - 40);
        int valEnd = Math.min(valOffset + 20, sql.length);
        String lineSeparator = System.getProperty("line.separator");
        buffer.append(lineSeparator);
        buffer.append(sql, valBegin, valEnd - valBegin);
        buffer.append(lineSeparator);
        for (; valBegin < valOffset; valBegin++) {
            buffer.append(' ');
        }
        buffer.append('^');

        return buffer.toString();
    }

    private void checkValidIdentifier(String name, SQLToken token)
            throws SQLException {
        if (token.value == SQLTokenizer.ASTERISK) {
            return;
        }
        if (token.value != SQLTokenizer.VALUE
                && token.value != SQLTokenizer.IDENTIFIER && token.value < 200) {
            throw createSyntaxError(token, MoonCode.STXADD_IDENT_EXPECT);
        }
        if (name.length() == 0) {
            throw createSyntaxError(token, MoonCode.STXADD_IDENT_EMPTY, name);
        }
        char firstChar = name.charAt(0);
        if (firstChar != '#' && firstChar < '@') {
            throw createSyntaxError(token, MoonCode.STXADD_IDENT_WRONG, name);
        }
    }

    private String getIdentifier(SQLToken token) throws SQLException {
        String name = token.getName(sql);
        checkValidIdentifier(name, token);
        return name;
    }

    private String nextIdentifier() throws SQLException {
        return getIdentifier(nextToken(MISSING_IDENTIFIER));
    }

    private String nextIdentiferPart(String name) throws SQLException {
        SQLToken token = nextToken();
        if (token != null && token.value == SQLTokenizer.POINT) {
            return nextIdentifier();
        } else {
            previousToken();
        }
        return name;
    }

    private boolean isKeyword(SQLToken token) {
        if (token == null) {
            return false;
        }
        switch (token.value) {
            case SQLTokenizer.SELECT:
            case SQLTokenizer.INSERT:
            case SQLTokenizer.UPDATE:
            case SQLTokenizer.UNION:
            case SQLTokenizer.FROM:
            case SQLTokenizer.WHERE:
            case SQLTokenizer.GROUP:
            case SQLTokenizer.HAVING:
            case SQLTokenizer.ORDER:
            case SQLTokenizer.COMMA:
            case SQLTokenizer.SET:
            case SQLTokenizer.JOIN:
            case SQLTokenizer.LIMIT:
                return true;
        }
        return false;
    }

    private SQLToken lastToken() {
        if (tokenIdx > tokens.size()) {
            return null;
        }
        return (SQLToken) tokens.get(tokenIdx - 1);
    }

    private void previousToken() {
        tokenIdx--;
    }

    private SQLToken nextToken() {
        if (tokenIdx >= tokens.size()) {
            tokenIdx++; 
            return null;
        }
        return (SQLToken) tokens.get(tokenIdx++);
    }

    private SQLToken nextToken(int[] validValues) throws SQLException {
        SQLToken token = nextToken();
        if (token == null) {
            throw createSyntaxError(token, validValues);
        }
        if (validValues == MISSING_EXPRESSION) {
            return token; 
        }
        if (validValues == MISSING_IDENTIFIER) {
            switch (token.value) {
                case SQLTokenizer.PARENTHESIS_L:
                case SQLTokenizer.PARENTHESIS_R:
                case SQLTokenizer.COMMA:
                    throw createSyntaxError(token, validValues);
            }
            return token;
        }
        for (int i = validValues.length - 1; i >= 0; i--) {
            if (token.value == validValues[i]) {
                return token;
            }
        }
        throw createSyntaxError(token, validValues);
    }

    private CommandSelect singleSelect() throws SQLException {
        CommandSelect selCmd = new CommandSelect(con.log);
        SQLToken token;
        Switch:
        while (true) {
            token = nextToken(MISSING_EXPRESSION);
            switch (token.value) {
                case SQLTokenizer.TOP:
                    token = nextToken(MISSING_EXPRESSION);
                    try {
                        int maxRows = Integer.parseInt(token.getName(sql));
                        selCmd.setMaxRows(maxRows);
                    } catch (NumberFormatException e) {
                        throw createSyntaxError(token, MoonCode.STXADD_NOT_NUMBER,
                                token.getName(sql));
                    }
                    break;
                case SQLTokenizer.ALL:
                    selCmd.setDistinct(false);
                    break;
                case SQLTokenizer.DISTINCT:
                    selCmd.setDistinct(true);
                    break;
                default:
                    previousToken();
                    break Switch;
            }
        }

        while (true) {
            Expression column = expression(selCmd, 0);
            selCmd.addColumnExpression(column);

            token = nextToken();
            if (token == null) {
                return selCmd; 
            }
            boolean as = false;
            if (token.value == SQLTokenizer.AS) {
                token = nextToken(MISSING_EXPRESSION);
                as = true;
            }

            if (as || (!isKeyword(token))) {
                String alias = getIdentifier(token);
                column.setAlias(alias);
                token = nextToken();
                if (token == null) {
                    return selCmd; 
                }
            }

            switch (token.value) {
                case SQLTokenizer.COMMA:
                    if (column == null) {
                        throw createSyntaxError(token, MISSING_EXPRESSION);
                    }
                    column = null;
                    break;
                case SQLTokenizer.FROM:
                    if (column == null) {
                        throw createSyntaxError(token, MISSING_EXPRESSION);
                    }
                    column = null;
                    from(selCmd);
                    return selCmd;

                default:
                    if (!isKeyword(token)) {
                        throw createSyntaxError(token, new int[]{
                            SQLTokenizer.COMMA, SQLTokenizer.FROM});
                    }
                    previousToken();
                    return selCmd;
            }
        }
    }

    final private CommandSelect select() throws SQLException {
        CommandSelect selCmd = singleSelect();
        SQLToken token = nextToken();

        UnionAll union = null;

        while (token != null && token.value == SQLTokenizer.UNION) {
            if (union == null) {
                union = new UnionAll();
                union.addDataSource(new ViewResult(con, selCmd));
                selCmd = new CommandSelect(con.log);
                selCmd.setSource(union);
                DataSources from = new DataSources();
                from.add(union);
                selCmd.setTables(from);
                selCmd.addColumnExpression(new ExpressionName("*"));
            }
            nextToken(MISSING_ALL);
            nextToken(MISSING_SELECT);
            union.addDataSource(new ViewResult(con, singleSelect()));
            token = nextToken();
        }
        if (token != null && token.value == SQLTokenizer.ORDER) {
            order(selCmd);
            token = nextToken();
        }
        if (token != null && token.value == SQLTokenizer.LIMIT) {
            limit(selCmd);
            token = nextToken();
        }
        previousToken();
        return selCmd;
    }

    private MoonCommand delete() throws SQLException {
        CommandDelete cmd = new CommandDelete(con.log);
        nextToken(MISSING_FROM);
        from(cmd);
        SQLToken token = nextToken();
        if (token != null) {
            if (token.value != SQLTokenizer.WHERE) {
                throw this.createSyntaxError(token, MISSING_WHERE);
            }
            where(cmd);
        }
        return cmd;
    }

    private MoonCommand truncate() throws SQLException {
        CommandDelete cmd = new CommandDelete(con.log);
        nextToken(MISSING_TABLE);
        from(cmd);
        return cmd;
    }

    private MoonCommand insert() throws SQLException {
        SQLToken token = nextToken(MISSING_INTO);
        CommandInsert cmd = new CommandInsert(con.log, nextIdentifier());

        int parthesisCount = 0;

        token = nextToken(MISSING_PARENTHESIS_VALUES_SELECT);
        if (token.value == SQLTokenizer.PARENTHESIS_L) {
            token = nextToken(MISSING_EXPRESSION);
            if (token.value == SQLTokenizer.SELECT) {
                parthesisCount++;
                cmd.noColumns = true;
            } else {
                previousToken();
                Expressions list = expressionParenthesisList(cmd);
                for (int i = 0; i < list.size(); i++) {
                    cmd.addColumnExpression(list.get(i));
                }
                token = nextToken(MISSING_PARENTHESIS_VALUES_SELECT);
            }
        } else {
            cmd.noColumns = true;
        }

        Switch:
        while (true) {
            switch (token.value) {
                case SQLTokenizer.VALUES -> {
                    token = nextToken(MISSING_PARENTHESIS_L);
                    cmd.addValues(expressionParenthesisList(cmd));
                    return cmd;
                }
                case SQLTokenizer.SELECT -> {
                    cmd.addValues(select());
                    while (parthesisCount-- > 0) {
                        nextToken(MISSING_PARENTHESIS_R);
                    }
                    return cmd;
                }
                case SQLTokenizer.PARENTHESIS_L -> {
                    token = nextToken(MISSING_PARENTHESIS_VALUES_SELECT);
                    parthesisCount++;
                    continue Switch;
                }
                default -> throw new Error();
            }
        }
    }

    private MoonCommand update() throws SQLException {
        CommandUpdate cmd = new CommandUpdate(con.log);
        DataSources tables = new DataSources();
        cmd.setTables(tables);
        cmd.setSource(rowSource(cmd, tables, 0));

        SQLToken token = nextToken(MISSING_SET);
        while (true) {
            token = nextToken();
            Expression dest = expressionSingle(cmd, token);
            if (dest.getType() != Expression.NAME) {
                throw createSyntaxError(token, MISSING_IDENTIFIER);
            }
            nextToken(MISSING_EQUALS);
            Expression src = expression(cmd, 0);
            cmd.addSetting(dest, src);
            token = nextToken();
            if (token == null) {
                break;
            }
            switch (token.value) {
                case SQLTokenizer.WHERE:
                    where(cmd);
                    return cmd;
                case SQLTokenizer.COMMA:
                    continue;
                default:
                    throw createSyntaxError(token, MISSING_WHERE_COMMA);
            }
        }
        return cmd;
    }

    private MoonCommand create() throws SQLException {
        while (true) {
            SQLToken token = nextToken(COMMANDS_CREATE);
            switch (token.value) {
                case SQLTokenizer.DATABASE -> {
                    return createDatabase();
                }
                case SQLTokenizer.TABLE -> {
                    return createTable();
                }
                case SQLTokenizer.VIEW -> {
                    return createView();
                }
                case SQLTokenizer.INDEX -> {
                    return createIndex(false);
                }
                case SQLTokenizer.PROCEDURE -> {
                    return createProcedure();
                }
                case SQLTokenizer.UNIQUE -> {
                    do {
                        token = nextToken(COMMANDS_CREATE_UNIQUE);
                    } while (token.value == SQLTokenizer.INDEX);
                    return createIndex(true);
                }
                case SQLTokenizer.NONCLUSTERED, SQLTokenizer.CLUSTERED -> {
                    continue;
                }
                default -> throw createSyntaxError(token, COMMANDS_CREATE);
            }
        }
    }

    private CommandCreateDatabase createDatabase() throws SQLException {
        SQLToken token = nextToken();
        if (token == null) {
            throw createSyntaxError(token, MISSING_EXPRESSION);
        }
        return new CommandCreateDatabase(con.log, token.getName(sql));
    }

    private CommandTable createTable() throws SQLException {
        String catalog;
        String tableName = catalog = nextIdentifier();
        tableName = nextIdentiferPart(tableName);
        if (tableName == null ? catalog == null : tableName.equals(catalog)) {
            catalog = null;
        }
        CommandTable cmdCreate = new CommandTable(con.log, catalog, tableName,
                SQLTokenizer.CREATE);
        SQLToken token = nextToken(MISSING_PARENTHESIS_L);

        nextCol:
        while (true) {
            token = nextToken(MISSING_EXPRESSION);

            String constraintName;
            if (token.value == SQLTokenizer.CONSTRAINT) {
                constraintName = nextIdentifier();
                token = nextToken(MISSING_KEYTYPE);
            } else {
                constraintName = null;
            }
            switch (token.value) {
                case SQLTokenizer.PRIMARY, SQLTokenizer.UNIQUE, SQLTokenizer.FOREIGN -> {
                    IndexDescription index = index(cmdCreate, token.value,
                            tableName, constraintName, null);
                    if (token.value == SQLTokenizer.FOREIGN) {
                        nextToken(MISSING_REFERENCES);
                        String pk = nextIdentifier();
                        Expressions expressions = new Expressions();
                        Strings columns = new Strings();
                        expressionDefList(cmdCreate, expressions, columns);
                        IndexDescription pkIndex = new IndexDescription(null, pk,
                                SQLTokenizer.UNIQUE, expressions, columns);
                        ForeignKey foreignKey = new ForeignKey(pk, pkIndex,
                                tableName, index);
                        cmdCreate.addForeingnKey(foreignKey);
                    } else {
                        cmdCreate.addIndex(index);
                    }

                    token = nextToken(MISSING_COMMA_PARENTHESIS);
                    switch (token.value) {
                        case SQLTokenizer.PARENTHESIS_R -> {
                            return cmdCreate;
                    }
                        case SQLTokenizer.COMMA -> {
                            continue;
                    }
                    }
                }
            }
            token = addColumn(token, cmdCreate);
            if (token == null) {
                throw createSyntaxError(token, MISSING_COMMA_PARENTHESIS);
            }
            switch (token.value) {
                case SQLTokenizer.PARENTHESIS_R -> {
                    return cmdCreate;
                }
                case SQLTokenizer.COMMA -> {
                    continue;
                }
                default -> throw createSyntaxError(token, MISSING_COMMA_PARENTHESIS);
            }
        }
    }

    private SQLToken addColumn(SQLToken token, CommandTable cmdCreate)
            throws SQLException {
        String colName = getIdentifier(token);
        Column col = datatype(false);
        col.setName(colName);

        token = nextToken();
        boolean nullableWasSet = false;
        boolean defaultWasSet = col.isAutoIncrement();
        while (true) {
            if (token == null) {
                cmdCreate.addColumn(col);
                return null;
            }
            switch (token.value) {
                case SQLTokenizer.PARENTHESIS_R, SQLTokenizer.COMMA -> {
                    cmdCreate.addColumn(col);
                    return token;
                }
                case SQLTokenizer.DEFAULT -> {
                    if (defaultWasSet) {
                        throw createSyntaxError(token, MISSING_COMMA_PARENTHESIS);
                    }
                    int offset = token.offset + token.length;
                    token = nextToken();
                    if (token != null) {
                        offset = token.offset;
                    }
                    previousToken();
                    Expression expr = expression(cmdCreate, 0);
                    SQLToken last = lastToken();
                    int length = last.offset + last.length - offset;
                    String def = new String(sql, offset, length);
                    col.setDefaultValue(expr, def);
                    defaultWasSet = true;
                }
                case SQLTokenizer.IDENTITY -> {
                    if (defaultWasSet) {
                        throw createSyntaxError(token, MISSING_COMMA_PARENTHESIS);
                    }
                    col.setAutoIncrement(true);
                    defaultWasSet = true;
                }
                case SQLTokenizer.NULL -> {
                    if (nullableWasSet) {
                        throw createSyntaxError(token, MISSING_COMMA_PARENTHESIS);
                    }
                    nullableWasSet = true;
                }
                case SQLTokenizer.NOT -> {
                    if (nullableWasSet) {
                        throw createSyntaxError(token, MISSING_COMMA_PARENTHESIS);
                    }
                    token = nextToken(MISSING_NULL);
                    col.setNullable(false);
                    nullableWasSet = true;
                }
                case SQLTokenizer.PRIMARY, SQLTokenizer.UNIQUE -> {
                    IndexDescription index = index(cmdCreate, token.value,
                            cmdCreate.name, null, colName);
                    cmdCreate.addIndex(index);
                }
                default -> throw createSyntaxError(token, MISSING_OPTIONS_DATATYPE);
            }
            token = nextToken();
        }
    }

    private IndexDescription index(MoonCommand cmd, int constraintType,
            String tableName, String contrainName, String columnName)
            throws SQLException {
        if (constraintType != SQLTokenizer.UNIQUE) {
            nextToken(MISSING_KEY);
        }
        SQLToken token = nextToken();
        if (token != null) {
            switch (token.value) {
                case SQLTokenizer.CLUSTERED, SQLTokenizer.NONCLUSTERED -> {
                }
                default -> previousToken();
            }
        } else {
            previousToken();
        }
        Strings columns = new Strings();
        Expressions expressions = new Expressions();
        if (columnName != null) {
            columns.add(columnName);
            expressions.add(new ExpressionName(columnName));
        } else {
            expressionDefList(cmd, expressions, columns);
        }
        return new IndexDescription(contrainName, tableName, constraintType,
                expressions, columns);
    }

    private Column datatype(boolean isEscape) throws SQLException {
        SQLToken token;
        int dataType;
        if (isEscape) {
            token = nextToken(MISSING_SQL_DATATYPE);
            switch (token.value) {
                case SQLTokenizer.SQL_BIGINT -> dataType = SQLTokenizer.BIGINT;
                case SQLTokenizer.SQL_BINARY -> dataType = SQLTokenizer.BINARY;
                case SQLTokenizer.SQL_BIT -> dataType = SQLTokenizer.BIT;
                case SQLTokenizer.SQL_CHAR -> dataType = SQLTokenizer.CHAR;
                case SQLTokenizer.SQL_DATE -> dataType = SQLTokenizer.DATE;
                case SQLTokenizer.SQL_DECIMAL -> dataType = SQLTokenizer.DECIMAL;
                case SQLTokenizer.SQL_DOUBLE -> dataType = SQLTokenizer.DOUBLE;
                case SQLTokenizer.SQL_FLOAT -> dataType = SQLTokenizer.FLOAT;
                case SQLTokenizer.SQL_INTEGER -> dataType = SQLTokenizer.INT;
                case SQLTokenizer.SQL_LONGVARBINARY -> dataType = SQLTokenizer.LONGVARBINARY;
                case SQLTokenizer.SQL_LONGVARCHAR -> dataType = SQLTokenizer.LONGVARCHAR;
                case SQLTokenizer.SQL_REAL -> dataType = SQLTokenizer.REAL;
                case SQLTokenizer.SQL_SMALLINT -> dataType = SQLTokenizer.SMALLINT;
                case SQLTokenizer.SQL_TIME -> dataType = SQLTokenizer.TIME;
                case SQLTokenizer.SQL_TIMESTAMP -> dataType = SQLTokenizer.TIMESTAMP;
                case SQLTokenizer.SQL_TINYINT -> dataType = SQLTokenizer.TINYINT;
                case SQLTokenizer.SQL_VARBINARY -> dataType = SQLTokenizer.VARBINARY;
                case SQLTokenizer.SQL_VARCHAR -> dataType = SQLTokenizer.VARCHAR;
                default -> throw new Error();
            }
        } else {
            token = nextToken(MISSING_DATATYPE);
            dataType = token.value;
        }
        Column col = new Column();
        if (dataType == SQLTokenizer.LONG) {
            token = nextToken();
            if (token != null && token.value == SQLTokenizer.RAW) {
                dataType = SQLTokenizer.LONGVARBINARY;
            } else {
                dataType = SQLTokenizer.LONGVARCHAR;
                previousToken();
            }
        }

        switch (dataType) {
            case SQLTokenizer.RAW:
                dataType = SQLTokenizer.VARBINARY;
            case SQLTokenizer.CHAR:
            case SQLTokenizer.VARCHAR:
            case SQLTokenizer.NCHAR:
            case SQLTokenizer.NVARCHAR:
            case SQLTokenizer.BINARY:
            case SQLTokenizer.VARBINARY: {
                token = nextToken();
                int displaySize;
                if (token == null || token.value != SQLTokenizer.PARENTHESIS_L) {
                    displaySize = 30;
                    previousToken();
                } else {
                    token = nextToken(MISSING_EXPRESSION);
                    try {
                        displaySize = Integer.parseInt(token.getName(sql));
                    } catch (Exception e) {
                        throw createSyntaxError(token, MISSING_NUMBERVALUE);
                    }
                    nextToken(MISSING_PARENTHESIS_R);
                }
                col.setPrecision(displaySize);
                break;
            }
            case SQLTokenizer.SYSNAME:
                col.setPrecision(255);
                dataType = SQLTokenizer.VARCHAR;
                break;
            case SQLTokenizer.COUNTER:
                col.setAutoIncrement(true);
                dataType = SQLTokenizer.INT;
                break;
            case SQLTokenizer.NUMERIC:
            case SQLTokenizer.DECIMAL:
                token = nextToken();
                if (token != null && token.value == SQLTokenizer.PARENTHESIS_L) {
                    token = nextToken(MISSING_EXPRESSION);
                    int value;
                    try {
                        value = Integer.parseInt(token.getName(sql));
                    } catch (Exception e) {
                        throw createSyntaxError(token, MISSING_NUMBERVALUE);
                    }
                    col.setPrecision(value);
                    token = nextToken(MISSING_COMMA_PARENTHESIS);
                    if (token.value == SQLTokenizer.COMMA) {
                        token = nextToken(MISSING_EXPRESSION);
                        try {
                            value = Integer.parseInt(token.getName(sql));
                        } catch (Exception e) {
                            throw createSyntaxError(token, MISSING_NUMBERVALUE);
                        }
                        col.setScale(value);
                        nextToken(MISSING_PARENTHESIS_R);
                    }
                } else {
                    col.setPrecision(18);
                    previousToken();
                }
                break;
        }
        col.setDataType(dataType);
        return col;
    }

    private CommandCreateView createView() throws SQLException {
        String viewName = nextIdentifier();

        nextToken(MISSING_AS);
        SQLToken token = nextToken(MISSING_SELECT);
        CommandCreateView cmd = new CommandCreateView(con.log, viewName);

        cmd.sql = new String(sql, token.offset, sql.length - token.offset);
        select(); 
        return cmd;
    }

    private CommandTable createIndex(boolean unique) throws SQLException {
        String indexName = nextIdentifier();
        nextToken(MISSING_ON);
        String catalog;
        String tableName = catalog = nextIdentifier();
        tableName = nextIdentiferPart(tableName);
        if (tableName == null ? catalog == null : tableName.equals(catalog)) {
            catalog = null;
        }
        CommandTable cmd = new CommandTable(con.log, catalog, tableName,
                SQLTokenizer.INDEX);
        Expressions expressions = new Expressions();
        Strings columns = new Strings();
        expressionDefList(cmd, expressions, columns);
        IndexDescription indexDesc = new IndexDescription(indexName, tableName,
                unique ? SQLTokenizer.UNIQUE : SQLTokenizer.INDEX, expressions,
                columns);
        Object[] param = {"Create Index"};
        throw MoonException.create(MoonCode.UNSUPPORTED_OPERATION, param);
    }

    private CommandCreateDatabase createProcedure() throws SQLException {
        Object[] param = {"Create Procedure"};
        throw MoonException.create(MoonCode.UNSUPPORTED_OPERATION, param);
    }

    private MoonCommand drop() throws SQLException {
        SQLToken tokenType = nextToken(COMMANDS_DROP);

        String catalog;
        String name = catalog = nextIdentifier();
        name = nextIdentiferPart(name);
        if (name == null ? catalog == null : name.equals(catalog)) {
            catalog = null;
        }

        switch (tokenType.value) {
            case SQLTokenizer.DATABASE, SQLTokenizer.TABLE, SQLTokenizer.VIEW, SQLTokenizer.INDEX, SQLTokenizer.PROCEDURE -> {
                return new CommandDrop(con.log, catalog, name, tokenType.value);
            }
            default -> throw createSyntaxError(tokenType, COMMANDS_DROP);
        }
    }

    private MoonCommand alter() throws SQLException {
        SQLToken tokenType = nextToken(COMMANDS_ALTER);
        String catalog;
        String tableName = catalog = nextIdentifier();
        switch (tokenType.value) {
            case SQLTokenizer.TABLE, SQLTokenizer.VIEW, SQLTokenizer.INDEX, SQLTokenizer.PROCEDURE -> {
                tableName = nextIdentiferPart(tableName);
                if (tableName == null ? catalog == null : tableName.equals(catalog)) {
                    catalog = null;
                }
            }
        }
        switch (tokenType.value) {
            case SQLTokenizer.TABLE -> {
                return alterTable(catalog, tableName);
            }
            default -> {
                Object[] param = {"ALTER " + tokenType.getName(sql)};
                throw MoonException.create(MoonCode.UNSUPPORTED_OPERATION,
                        param);
            }

        }
    }

    MoonCommand alterTable(String catalog, String name) throws SQLException {
        SQLToken tokenType = nextToken(MISSING_ADD_ALTER_DROP);
        CommandTable cmd = new CommandTable(con.log, catalog, name,
                tokenType.value);
        switch (tokenType.value) {
            case SQLTokenizer.ADD -> {
                SQLToken token;
                do {
                    token = nextToken(MISSING_IDENTIFIER);
                    token = addColumn(token, cmd);
                } while (token != null && token.value == SQLTokenizer.COMMA);

                return cmd;
            }
            default -> {
                Object[] param = {"ALTER TABLE " + tokenType.getName(sql)};
                throw MoonException.create(MoonCode.UNSUPPORTED_OPERATION,
                        param);
            }

        }
    }

    private CommandSet set() throws SQLException {
        SQLToken token = nextToken(COMMANDS_SET);
        switch (token.value) {
            case SQLTokenizer.TRANSACTION -> {
                return setTransaction();
            }
            default -> throw new Error();
        }
    }

    private CommandSet setTransaction() throws SQLException {
        SQLToken token = nextToken(MISSING_ISOLATION);
        token = nextToken(MISSING_LEVEL);
        token = nextToken(COMMANDS_TRANS_LEVEL);
        CommandSet cmd = new CommandSet(con.log, SQLTokenizer.LEVEL);
        switch (token.value) {
            case SQLTokenizer.READ -> {
                token = nextToken(MISSING_COMM_UNCOMM);
                switch (token.value) {
                    case SQLTokenizer.COMMITTED:
                        cmd.isolationLevel = Connection.TRANSACTION_READ_COMMITTED;
                        break;
                    case SQLTokenizer.UNCOMMITTED:
                        cmd.isolationLevel = Connection.TRANSACTION_READ_UNCOMMITTED;
                        break;
                    default:
                        throw new Error();
                }
                return cmd;
            }
            case SQLTokenizer.REPEATABLE -> {
                token = nextToken(MISSING_READ);
                cmd.isolationLevel = Connection.TRANSACTION_REPEATABLE_READ;
                return cmd;
            }
            case SQLTokenizer.SERIALIZABLE -> {
                cmd.isolationLevel = Connection.TRANSACTION_SERIALIZABLE;
                return cmd;
            }
            default -> throw new Error();
        }

    }

    private MoonCommand execute() throws SQLException {

        throw MoonException.create(MoonCode.UNSUPPORTED_OPERATION,
                "Execute");
    }

    private Expressions expressionParenthesisList(MoonCommand cmd)
            throws SQLException {
        Expressions list = new Expressions();
        {
            SQLToken token = nextToken();
            if (token != null && token.value == SQLTokenizer.PARENTHESIS_R) {
                return list;
            }
            previousToken();
        }
        while (true) {
            list.add(expression(cmd, 0));
            SQLToken token = nextToken(MISSING_COMMA_PARENTHESIS);
            switch (token.value) {
                case SQLTokenizer.PARENTHESIS_R -> {
                    return list;
                }
                case SQLTokenizer.COMMA -> {
                    continue;
                }
                default -> throw new Error();
            }
        }
    }

    private Expressions expressionTokenList(MoonCommand cmd, int listType)
            throws SQLException {
        Expressions list = new Expressions();
        while (true) {
            Expression expr = expression(cmd, 0);
            list.add(expr);
            SQLToken token = nextToken();

            if (listType == SQLTokenizer.ORDER && token != null) {
                switch (token.value) {
                    case SQLTokenizer.DESC:
                        expr.setAlias(SQLTokenizer.DESC_STR);
                    case SQLTokenizer.ASC:
                        token = nextToken();
                }
            }

            if (token == null) {
                previousToken();
                return list;
            }

            switch (token.value) {
                case SQLTokenizer.COMMA -> {
                    continue;
                }
                default -> {
                    if (isKeyword(token)) {
                        previousToken();
                        return list;
                    }
                    throw createSyntaxError(token, MISSING_TOKEN_LIST);
                }
            }
        }
    }

    private void expressionDefList(MoonCommand cmd, Expressions expressions,
            Strings columns) throws SQLException {
        SQLToken token = nextToken();
        if (token.value != SQLTokenizer.PARENTHESIS_L) {
            throw createSyntaxError(token, MISSING_PARENTHESIS_L);
        }
        Loop:
        while (true) {
            int offset = token.offset + token.length;
            token = nextToken();
            if (token != null) {
                offset = token.offset;
            }
            previousToken();

            expressions.add(expression(cmd, 0));
            SQLToken last = lastToken();
            int length = last.offset + last.length - offset;
            columns.add(new String(sql, offset, length));

            token = nextToken(MISSING_COMMA_PARENTHESIS);
            switch (token.value) {
                case SQLTokenizer.PARENTHESIS_R -> {
                    break Loop;
                }
                case SQLTokenizer.COMMA -> {
                    continue;
                }
                default -> throw new Error();
            }
        }
    }

    private Expression expression(MoonCommand cmd, int previousOperationLevel)
            throws SQLException {
        SQLToken token = nextToken(MISSING_EXPRESSION);
        Expression leftExpr;
        switch (token.value) {
            case SQLTokenizer.NOT -> leftExpr = new ExpressionArithmetic(expression(cmd,
                        ExpressionArithmetic.NOT / 10), ExpressionArithmetic.NOT);
            case SQLTokenizer.MINUS -> leftExpr = new ExpressionArithmetic(expression(cmd,
                        ExpressionArithmetic.NEGATIVE / 10),
                        ExpressionArithmetic.NEGATIVE);
            case SQLTokenizer.TILDE -> leftExpr = new ExpressionArithmetic(expression(cmd,
                        ExpressionArithmetic.BIT_NOT / 10),
                        ExpressionArithmetic.BIT_NOT);
            case SQLTokenizer.PARENTHESIS_L -> {
                leftExpr = expression(cmd, 0);
                token = nextToken(MISSING_PARENTHESIS_R);
            }
            default -> leftExpr = expressionSingle(cmd, token);
        }
        boolean isNot = false;
        while ((token = nextToken()) != null) {
            Expression rightExpr;
            int operation = ExpressionArithmetic
                    .getOperationFromToken(token.value);
            int level = operation / 10;
            if (previousOperationLevel >= level) {
                previousToken();
                return leftExpr;
            }
            switch (token.value) {
                case SQLTokenizer.PLUS, SQLTokenizer.MINUS, SQLTokenizer.ASTERISK, SQLTokenizer.SLACH, SQLTokenizer.PERCENT, SQLTokenizer.EQUALS, SQLTokenizer.LESSER, SQLTokenizer.LESSER_EQU, SQLTokenizer.GREATER, SQLTokenizer.GREATER_EQU, SQLTokenizer.UNEQUALS, SQLTokenizer.LIKE, SQLTokenizer.OR, SQLTokenizer.AND, SQLTokenizer.BIT_AND, SQLTokenizer.BIT_OR, SQLTokenizer.BIT_XOR -> {
                    rightExpr = expression(cmd, level);
                    leftExpr = new ExpressionArithmetic(leftExpr, rightExpr,
                            operation);
                }
                case SQLTokenizer.BETWEEN -> {
                    rightExpr = expression(cmd, ExpressionArithmetic.AND);
                    nextToken(MISSING_AND);
                    Expression rightExpr2 = expression(cmd, level);
                    leftExpr = new ExpressionArithmetic(leftExpr, rightExpr,
                            rightExpr2, operation);
                }
                case SQLTokenizer.IN -> {
                    nextToken(MISSING_PARENTHESIS_L);
                    token = nextToken(MISSING_EXPRESSION);
                    if (token.value == SQLTokenizer.SELECT) {
                        CommandSelect cmdSel = select();
                        leftExpr = new ExpressionInSelect(con, leftExpr, cmdSel,
                                operation);
                        nextToken(MISSING_PARENTHESIS_R);
                    } else {
                        previousToken();
                        Expressions list = expressionParenthesisList(cmd);
                        leftExpr = new ExpressionArithmetic(leftExpr, list,
                                operation);
                    }
                }
                case SQLTokenizer.IS -> {
                    token = nextToken(MISSING_NOT_NULL);
                    if (token.value == SQLTokenizer.NOT) {
                        nextToken(MISSING_NULL);
                        operation++;
                    }
                    leftExpr = new ExpressionArithmetic(leftExpr, operation);
                }
                case SQLTokenizer.NOT -> {
                    token = nextToken(MISSING_BETWEEN_IN);
                    previousToken();
                    isNot = true;
                    continue;
                }
                default -> {
                    previousToken();
                    return leftExpr;
                }
            }
            if (isNot) {
                isNot = false;
                leftExpr = new ExpressionArithmetic(leftExpr,
                        ExpressionArithmetic.NOT);
            }
        }
        previousToken();
        return leftExpr;
    }

    private Expression expressionSingle(MoonCommand cmd, SQLToken token)
            throws SQLException {
        boolean isMinus = false;
        if (token != null) {
            switch (token.value) {
                case SQLTokenizer.NULL:
                    return new ExpressionValue(null, SQLTokenizer.NULL);
                case SQLTokenizer.STRING:
                    return new ExpressionValue(token.getName(null),
                            SQLTokenizer.VARCHAR);
                case SQLTokenizer.IDENTIFIER: {
                    String name = getIdentifier(token);
                    ExpressionName expr = new ExpressionName(name);
                    SQLToken token2 = nextToken();
                    if (token2 != null && token2.value == SQLTokenizer.POINT) {
                        expr.setNameAfterTableAlias(nextIdentifier());
                    } else {
                        previousToken();
                    }
                    return expr;
                }
                case SQLTokenizer.TRUE:
                    return new ExpressionValue(Boolean.TRUE, SQLTokenizer.BOOLEAN);
                case SQLTokenizer.FALSE:
                    return new ExpressionValue(Boolean.FALSE, SQLTokenizer.BOOLEAN);
                case SQLTokenizer.ESCAPE_L: {
                    token = nextToken(COMMANDS_ESCAPE);
                    SQLToken para = nextToken(MISSING_EXPRESSION);
                    Expression expr;
                    switch (token.value) {
                        case SQLTokenizer.D: 
                            expr = new ExpressionValue(DateTime.valueOf(
                                    para.getName(sql), SQLTokenizer.DATE),
                                    SQLTokenizer.DATE);
                            break;
                        case SQLTokenizer.T:
                            expr = new ExpressionValue(DateTime.valueOf(
                                    para.getName(sql), SQLTokenizer.TIME),
                                    SQLTokenizer.TIME);
                            break;
                        case SQLTokenizer.TS: 
                            expr = new ExpressionValue(DateTime.valueOf(
                                    para.getName(sql), SQLTokenizer.TIMESTAMP),
                                    SQLTokenizer.TIMESTAMP);
                            break;
                        case SQLTokenizer.FN: 
                            nextToken(MISSING_PARENTHESIS_L);
                            expr = function(cmd, para, true);
                            break;
                        case SQLTokenizer.CALL: 
                            throw new java.lang.UnsupportedOperationException(
                                    "call escape sequence");
                        default:
                            throw new Error();
                    }
                    token = nextToken(ESCAPE_MISSING_CLOSE);
                    return expr;
                }
                case SQLTokenizer.QUESTION:
                    ExpressionValue param = new ExpressionValue();
                    cmd.addParameter(param);
                    return param;
                case SQLTokenizer.CASE:
                    return caseExpr(cmd);
                case SQLTokenizer.MINUS:
                case SQLTokenizer.PLUS:
                    do {
                        if (token.value == SQLTokenizer.MINUS) {
                            isMinus = !isMinus;
                        }
                        token = nextToken();
                        if (token == null) {
                            throw createSyntaxError(token, MISSING_EXPRESSION);
                        }
                    } while (token.value == SQLTokenizer.MINUS
                            || token.value == SQLTokenizer.PLUS);
                default:
                    SQLToken token2 = nextToken();
                    if (token2 != null
                            && token2.value == SQLTokenizer.PARENTHESIS_L) {
                        if (isMinus) {
                            return new ExpressionArithmetic(function(cmd, token,
                                    false), ExpressionArithmetic.NEGATIVE);
                        }
                        return function(cmd, token, false);
                    } else {
                        char chr1 = sql[token.offset];
                        if (chr1 == '$') {
                            previousToken();
                            String tok = new String(sql, token.offset + 1,
                                    token.length - 1);
                            if (isMinus) {
                                tok = "-" + tok;
                            }
                            return new ExpressionValue(new Money(
                                    Double.parseDouble(tok)), SQLTokenizer.MONEY);
                        }
                        String tok = new String(sql, token.offset, token.length);
                        if ((chr1 >= '0' && '9' >= chr1) || chr1 == '.') {
                            previousToken();
                            if (token.length > 1
                                    && (sql[token.offset + 1] | 0x20) == 'x') {
                                 if (isMinus) {
                                    throw createSyntaxError(token,
                                            MoonCode.STXADD_OPER_MINUS);
                                }
                                return new ExpressionValue(MoonUtils.hex2bytes(sql,
                                        token.offset + 2, token.length - 2),
                                        SQLTokenizer.VARBINARY);
                            }
                            if (isMinus) {
                                tok = "-" + tok;
                            }
                            if (MoonUtils.indexOf('.', sql, token.offset, token.length) >= 0
                                    || MoonUtils.indexOf('e', sql, token.offset,
                                            token.length) >= 0) {
                                return new ExpressionValue(Double.valueOf(tok),
                                        SQLTokenizer.DOUBLE);
                            } else {
                                try {
                                    return new ExpressionValue(Integer.valueOf(tok),
                                            SQLTokenizer.INT);
                                } catch (NumberFormatException e) {
                                    return new ExpressionValue(Long.valueOf(tok),
                                            SQLTokenizer.BIGINT);
                                }
                            }
                        } else {
                            checkValidIdentifier(tok, token);
                            ExpressionName expr = new ExpressionName(tok);
                            if (token2 != null
                                    && token2.value == SQLTokenizer.POINT) {
                                expr.setNameAfterTableAlias(nextIdentifier());
                            } else {
                                previousToken();
                            }
                            if (isMinus) {
                                return new ExpressionArithmetic(expr,
                                        ExpressionArithmetic.NEGATIVE);
                            }
                            return expr;
                        }
                    }
            }
        }
        return null;
    }

    FunctionCase caseExpr(final MoonCommand cmd) throws SQLException {
        FunctionCase expr = new FunctionCase();
        SQLToken token = nextToken(MISSING_EXPRESSION);

        Expression input = null;
        if (token.value != SQLTokenizer.WHEN) {
            previousToken();
            input = expression(cmd, 0);
            token = nextToken(MISSING_WHEN_ELSE_END);
        }

        while (true) {
            switch (token.value) {
                case SQLTokenizer.WHEN -> {
                    Expression condition = expression(cmd, 0);
                    if (input != null) {
                        condition = new ExpressionArithmetic(input, condition,
                                ExpressionArithmetic.EQUALS);
                    }
                    nextToken(MISSING_THEN);
                    Expression result = expression(cmd, 0);
                    expr.addCase(condition, result);
                }
                case SQLTokenizer.ELSE -> expr.setElseResult(expression(cmd, 0));
                case SQLTokenizer.END -> {
                    expr.setEnd();
                    return expr;
                }
                default -> throw new Error();
            }
            token = nextToken(MISSING_WHEN_ELSE_END);
        }
    }

    private Expression function(MoonCommand cmd, SQLToken token, boolean isEscape)
            throws SQLException {
        Expression expr;
        switch (token.value) {
            case SQLTokenizer.CONVERT -> {
                Column col;
                Expression style = null;
                if (isEscape) {
                    expr = expression(cmd, 0);
                    nextToken(MISSING_COMMA);
                    col = datatype(isEscape);
                } else {
                    col = datatype(isEscape);
                    nextToken(MISSING_COMMA);
                    expr = expression(cmd, 0);
                    token = nextToken(MISSING_COMMA_PARENTHESIS);
                    if (token.value == SQLTokenizer.COMMA) {
                        style = expression(cmd, 0);
                    } else {
                        previousToken();
                    }
                }
                nextToken(MISSING_PARENTHESIS_R);
                return new FunctionConvert(col, expr, style);
            }
            case SQLTokenizer.CAST -> {
                expr = expression(cmd, 0);
                nextToken(MISSING_AS);
                Column col = datatype(false);
                nextToken(MISSING_PARENTHESIS_R);
                return new FunctionConvert(col, expr, null);
            }
            case SQLTokenizer.TIMESTAMPDIFF -> {
                token = nextToken(MISSING_INTERVALS);
                nextToken(MISSING_COMMA);
                expr = expression(cmd, 0);
                nextToken(MISSING_COMMA);
                expr = new FunctionTimestampDiff(token.value, expr,
                        expression(cmd, 0));
                nextToken(MISSING_PARENTHESIS_R);
                return expr;
            }
            case SQLTokenizer.TIMESTAMPADD -> {
                token = nextToken(MISSING_INTERVALS);
                nextToken(MISSING_COMMA);
                expr = expression(cmd, 0);
                nextToken(MISSING_COMMA);
                expr = new FunctionTimestampAdd(token.value, expr,
                        expression(cmd, 0));
                nextToken(MISSING_PARENTHESIS_R);
                return expr;
            }
        }
        Expressions paramList = expressionParenthesisList(cmd);
        int paramCount = paramList.size();
        Expression[] params = paramList.toArray();
        boolean invalidParamCount;
        switch (token.value) {
            case SQLTokenizer.ABS -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionAbs();
            }
            case SQLTokenizer.ACOS -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionACos();
            }
            case SQLTokenizer.ASIN -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionASin();
            }
            case SQLTokenizer.ATAN -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionATan();
            }
            case SQLTokenizer.ATAN2 -> {
                invalidParamCount = (paramCount != 2);
                expr = new FunctionATan2();
            }
            case SQLTokenizer.CEILING -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionCeiling();
            }
            case SQLTokenizer.COS -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionCos();
            }
            case SQLTokenizer.COT -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionCot();
            }
            case SQLTokenizer.DEGREES -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionDegrees();
            }
            case SQLTokenizer.EXP -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionExp();
            }
            case SQLTokenizer.FLOOR -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionFloor();
            }
            case SQLTokenizer.LOG -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionLog();
            }
            case SQLTokenizer.LOG10 -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionLog10();
            }
            case SQLTokenizer.MOD -> {
                invalidParamCount = (paramCount != 2);
                expr = new FunctionMod();
            }
            case SQLTokenizer.PI -> {
                invalidParamCount = (paramCount != 0);
                expr = new FunctionPI();
            }
            case SQLTokenizer.POWER -> {
                invalidParamCount = (paramCount != 2);
                expr = new FunctionPower();
            }
            case SQLTokenizer.RADIANS -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionRadians();
            }
            case SQLTokenizer.RAND -> {
                invalidParamCount = (paramCount != 0) && (paramCount != 1);
                expr = new FunctionRand();
            }
            case SQLTokenizer.ROUND -> {
                invalidParamCount = (paramCount != 2);
                expr = new FunctionRound();
            }
            case SQLTokenizer.SIN -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionSin();
            }
            case SQLTokenizer.SIGN -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionSign();
            }
            case SQLTokenizer.SQRT -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionSqrt();
            }
            case SQLTokenizer.TAN -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionTan();
            }
            case SQLTokenizer.TRUNCATE -> {
                invalidParamCount = (paramCount != 2);
                expr = new FunctionTruncate();
            }

            case SQLTokenizer.ASCII -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionAscii();
            }
            case SQLTokenizer.BITLEN -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionBitLen();
            }
            case SQLTokenizer.CHARLEN, SQLTokenizer.CHARACTLEN -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionCharLen();
            }
            case SQLTokenizer.CHAR -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionChar();
            }
            case SQLTokenizer.CONCAT -> {
                if (paramCount != 2) {
                    invalidParamCount = true;
                    expr = null;
                    break;
                }
                invalidParamCount = false;
                expr = new ExpressionArithmetic(params[0], params[1],
                        ExpressionArithmetic.ADD);
            }
            case SQLTokenizer.DIFFERENCE -> {
                invalidParamCount = (paramCount != 2);
                expr = new FunctionDifference();
            }
            case SQLTokenizer.INSERT -> {
                invalidParamCount = (paramCount != 4);
                expr = new FunctionInsert();
            }
            case SQLTokenizer.LCASE -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionLCase();
            }
            case SQLTokenizer.LEFT -> {
                invalidParamCount = (paramCount != 2);
                expr = new FunctionLeft();
            }
            case SQLTokenizer.LENGTH -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionLength();
            }
            case SQLTokenizer.LOCATE -> {
                invalidParamCount = (paramCount != 2) && (paramCount != 3);
                expr = new FunctionLocate();
            }
            case SQLTokenizer.LTRIM -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionLTrim();
            }
            case SQLTokenizer.OCTETLEN -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionOctetLen();
            }
            case SQLTokenizer.REPEAT -> {
                invalidParamCount = (paramCount != 2);
                expr = new FunctionRepeat();
            }
            case SQLTokenizer.REPLACE -> {
                invalidParamCount = (paramCount != 3);
                expr = new FunctionReplace();
            }
            case SQLTokenizer.RIGHT -> {
                invalidParamCount = (paramCount != 2);
                expr = new FunctionRight();
            }
            case SQLTokenizer.RTRIM -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionRTrim();
            }
            case SQLTokenizer.SPACE -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionSpace();
            }
            case SQLTokenizer.SOUNDEX -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionSoundex();
            }
            case SQLTokenizer.SUBSTRING -> {
                invalidParamCount = (paramCount != 3);
                expr = new FunctionSubstring();
            }
            case SQLTokenizer.UCASE -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionUCase();
            }
            case SQLTokenizer.CURDATE, SQLTokenizer.CURRENTDATE -> {
                invalidParamCount = (paramCount != 0);
                expr = new ExpressionValue(new DateTime(DateTime.now(),
                        SQLTokenizer.DATE), SQLTokenizer.DATE);
            }

            case SQLTokenizer.CURTIME -> {
                invalidParamCount = (paramCount != 0);
                expr = new ExpressionValue(new DateTime(DateTime.now(),
                        SQLTokenizer.TIME), SQLTokenizer.TIME);
            }
            case SQLTokenizer.DAYOFMONTH -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionDayOfMonth();
            }
            case SQLTokenizer.DAYOFWEEK -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionDayOfWeek();
            }
            case SQLTokenizer.DAYOFYEAR -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionDayOfYear();
            }
            case SQLTokenizer.HOUR -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionHour();
            }
            case SQLTokenizer.MINUTE -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionMinute();
            }
            case SQLTokenizer.MONTH -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionMonth();
            }
            case SQLTokenizer.NOW -> {
                invalidParamCount = (paramCount != 0);
                expr = new ExpressionValue(new DateTime(DateTime.now(),
                        SQLTokenizer.TIMESTAMP), SQLTokenizer.TIMESTAMP);
            }
            case SQLTokenizer.YEAR -> {
                invalidParamCount = (paramCount != 1);
                expr = new FunctionYear();
            }
            case SQLTokenizer.IIF -> {
                invalidParamCount = (paramCount != 3);
                expr = new FunctionIIF();
            }
            case SQLTokenizer.SWITCH -> {
                invalidParamCount = (paramCount % 2 != 0);
                FunctionCase exprCase = new FunctionCase();
                for (int i = 0; i < paramCount - 1; i += 2) {
                    exprCase.addCase(params[i], params[i + 1]);
                }
                exprCase.setEnd();
                expr = exprCase;
            }

            case SQLTokenizer.IFNULL -> {
                switch (paramCount) {
                    case 1 -> {
                        return new ExpressionArithmetic(params[0],
                                ExpressionArithmetic.ISNULL);
                }
                    case 2 -> {
                        invalidParamCount = false;
                        expr = new FunctionIIF();
                        Expression[] newParams = new Expression[3];
                        newParams[0] = new ExpressionArithmetic(params[0],
                                ExpressionArithmetic.ISNULL);
                        newParams[1] = params[1];
                        newParams[2] = params[0];
                        params = newParams;
                        paramCount = 3;
                }
                    default -> {
                        invalidParamCount = true;
                        expr = null; 
                }
                }
            }
            case SQLTokenizer.COUNT -> {
                invalidParamCount = (paramCount != 1);
                if (params[0].getType() == Expression.NAME) {
                    ExpressionName param = (ExpressionName) params[0];
                    if ("*".equals(param.getName())
                            && param.getTableAlias() == null) {
                        params[0] = new ExpressionValue("*", SQLTokenizer.VARCHAR);
                    }
                }
                expr = new ExpressionName(Expression.COUNT);
            }
            case SQLTokenizer.SUM -> {
                invalidParamCount = (paramCount != 1);
                expr = new ExpressionName(Expression.SUM);
            }

            case SQLTokenizer.MAX -> {
                invalidParamCount = (paramCount != 1);
                expr = new ExpressionName(Expression.MAX);
            }
            case SQLTokenizer.MIN -> {
                invalidParamCount = (paramCount != 1);
                expr = new ExpressionName(Expression.MIN);
            }
            case SQLTokenizer.FIRST -> {
                invalidParamCount = (paramCount != 1);
                expr = new ExpressionName(Expression.FIRST);
            }
            case SQLTokenizer.LAST -> {
                invalidParamCount = (paramCount != 1);
                expr = new ExpressionName(Expression.LAST);
            }
            case SQLTokenizer.AVG -> {
                if (paramCount != 1) {
                    invalidParamCount = true;
                    expr = null;
                    break;
                }
                expr = new ExpressionName(Expression.SUM);
                expr.setParams(params);
                Expression expr2 = new ExpressionName(Expression.COUNT);
                expr2.setParams(params);
                expr = new ExpressionArithmetic(expr, expr2,
                        ExpressionArithmetic.DIV);
                return expr;
            }
            default -> throw createSyntaxError(token, MoonCode.STXADD_FUNC_UNKNOWN);
        }
        if (invalidParamCount) {
            throw createSyntaxError(token, MoonCode.STXADD_PARAM_INVALID_COUNT);
        }
        expr.setParams(params);
        return expr;
    }

    private RowSource tableSource(MoonCommand cmd, DataSources tables)
            throws SQLException {
        SQLToken token = nextToken(MISSING_EXPRESSION);
        switch (token.value) {
            case SQLTokenizer.PARENTHESIS_L -> { 
                return rowSource(cmd, tables, SQLTokenizer.PARENTHESIS_R);
            } 
            case SQLTokenizer.ESCAPE_L -> {
                token = nextToken(MISSING_OJ);
                return rowSource(cmd, tables, SQLTokenizer.ESCAPE_R);
            }
            case SQLTokenizer.SELECT -> {
                ViewResult viewResult = new ViewResult(con, select());
                tables.add(viewResult);
                return viewResult;
            }
        }
        String catalog = null;
        String name = getIdentifier(token);
        token = nextToken();
        if (token != null && token.value == SQLTokenizer.POINT) {
            catalog = name;
            name = nextIdentifier();
            token = nextToken();
        }
        View tableView = Database.getTableView(con, catalog, name);
        TableViewResult table = TableViewResult.createResult(tableView);
        tables.add(table);

        if (token != null && token.value == SQLTokenizer.AS) {
            token = nextToken(MISSING_EXPRESSION);
            table.setAlias(token.getName(sql));
        } else {
            previousToken();
        }
        return table;
    }

    private Join join(MoonCommand cmd, DataSources tables, RowSource left, int type)
            throws SQLException {
        RowSource right = rowSource(cmd, tables, 0);
        SQLToken token = nextToken();

        while (true) {
            if (token == null) {
                throw createSyntaxError(token, MoonCode.STXADD_JOIN_INVALID);
            }

            switch (token.value) {
                case SQLTokenizer.ON -> {
                    if (type == Join.RIGHT_JOIN) {
                        return new Join(Join.LEFT_JOIN, right, left, expression(
                                cmd, 0));
                    }
                    return new Join(type, left, right, expression(cmd, 0));
                }
                default -> {
                    if (!right.hasAlias()) {
                        right.setAlias(token.getName(sql));
                        token = nextToken();
                        continue;
                    }
                    throw createSyntaxError(token, MISSING_ON);
                }
            }
        }
    }

    private RowSource rowSource(MoonCommand cmd, DataSources tables, int parenthesis)
            throws SQLException {
        RowSource fromSource = null;
        fromSource = tableSource(cmd, tables);

        while (true) {
            SQLToken token = nextToken();
            if (token == null) {
                return fromSource;
            }
            switch (token.value) {
                case SQLTokenizer.ON:
                    previousToken();
                    return fromSource;
                case SQLTokenizer.CROSS:
                    nextToken(MISSING_JOIN);
                case SQLTokenizer.COMMA:
                    fromSource = new Join(Join.CROSS_JOIN, fromSource, rowSource(
                            cmd, tables, 0), null);
                    break;
                case SQLTokenizer.INNER:
                    nextToken(MISSING_JOIN);
                case SQLTokenizer.JOIN:
                    fromSource = join(cmd, tables, fromSource, Join.INNER_JOIN);
                    break;
                case SQLTokenizer.LEFT:
                    token = nextToken(MISSING_OUTER_JOIN);
                    if (token.value == SQLTokenizer.OUTER) {
                        token = nextToken(MISSING_JOIN);
                    }
                    fromSource = join(cmd, tables, fromSource, Join.LEFT_JOIN);
                    break;
                case SQLTokenizer.RIGHT:
                    token = nextToken(MISSING_OUTER_JOIN);
                    if (token.value == SQLTokenizer.OUTER) {
                        token = nextToken(MISSING_JOIN);
                    }
                    fromSource = join(cmd, tables, fromSource, Join.RIGHT_JOIN);
                    break;
                case SQLTokenizer.FULL:
                    token = nextToken(MISSING_OUTER_JOIN);
                    if (token.value == SQLTokenizer.OUTER) {
                        token = nextToken(MISSING_JOIN);
                    }
                    fromSource = join(cmd, tables, fromSource, Join.FULL_JOIN);
                    break;
                case SQLTokenizer.PARENTHESIS_R:
                case SQLTokenizer.ESCAPE_R:
                    if (parenthesis == token.value) {
                        return fromSource;
                    }
                    if (parenthesis == 0) {
                        previousToken();
                        return fromSource;
                    }
                    throw createSyntaxError(token, MoonCode.STXADD_FROM_PAR_CLOSE);
                default:
                    if (isKeyword(token)) {
                        previousToken();
                        return fromSource;
                    }
                    if (!fromSource.hasAlias()) {
                        fromSource.setAlias(token.getName(sql));
                        break;
                    }
                    throw createSyntaxError(token, new int[]{SQLTokenizer.COMMA,
                        SQLTokenizer.GROUP, SQLTokenizer.ORDER,
                        SQLTokenizer.HAVING});
            }
        }
    }

    private void from(CommandSelect cmd) throws SQLException {
        DataSources tables = new DataSources();
        cmd.setTables(tables);
        cmd.setSource(rowSource(cmd, tables, 0));

        SQLToken token;
        while (null != (token = nextToken())) {
            switch (token.value) {
                case SQLTokenizer.WHERE -> where(cmd);
                case SQLTokenizer.GROUP -> group(cmd);
                case SQLTokenizer.HAVING -> having(cmd);
                default -> {
                    previousToken();
                    return;
                }
            }
        }
    }

    private void order(CommandSelect cmd) throws SQLException {
        nextToken(MISSING_BY);
        cmd.setOrder(expressionTokenList(cmd, SQLTokenizer.ORDER));
    }

    private void limit(CommandSelect selCmd) throws SQLException {
        SQLToken token = nextToken(MISSING_EXPRESSION);
        try {
            int maxRows = Integer.parseInt(token.getName(sql));
            selCmd.setMaxRows(maxRows);
        } catch (NumberFormatException e) {
            throw createSyntaxError(token, MoonCode.STXADD_NOT_NUMBER,
                    token.getName(sql));
        }
    }

    private void group(CommandSelect cmd) throws SQLException {
        nextToken(MISSING_BY);
        cmd.setGroup(expressionTokenList(cmd, SQLTokenizer.GROUP));
    }

    private void where(CommandSelect cmd) throws SQLException {
        cmd.setWhere(expression(cmd, 0));
    }

    private void having(CommandSelect cmd) throws SQLException {
        cmd.setHaving(expression(cmd, 0));
    }

    private static final int[] COMMANDS = {SQLTokenizer.SELECT,
        SQLTokenizer.DELETE, SQLTokenizer.INSERT, SQLTokenizer.UPDATE,
        SQLTokenizer.CREATE, SQLTokenizer.DROP, SQLTokenizer.ALTER,
        SQLTokenizer.SET, SQLTokenizer.USE, SQLTokenizer.EXECUTE,
        SQLTokenizer.TRUNCATE};
    private static final int[] COMMANDS_ESCAPE = {SQLTokenizer.D,
        SQLTokenizer.T, SQLTokenizer.TS, SQLTokenizer.FN, SQLTokenizer.CALL};
    private static final int[] COMMANDS_ALTER = {SQLTokenizer.DATABASE,
        SQLTokenizer.TABLE, SQLTokenizer.VIEW, SQLTokenizer.PROCEDURE,};
    private static final int[] COMMANDS_CREATE = {SQLTokenizer.DATABASE,
        SQLTokenizer.TABLE, SQLTokenizer.VIEW, SQLTokenizer.INDEX,
        SQLTokenizer.PROCEDURE, SQLTokenizer.UNIQUE,
        SQLTokenizer.CLUSTERED, SQLTokenizer.NONCLUSTERED};
    private static final int[] COMMANDS_DROP = {SQLTokenizer.DATABASE,
        SQLTokenizer.TABLE, SQLTokenizer.VIEW, SQLTokenizer.INDEX,
        SQLTokenizer.PROCEDURE};
    private static final int[] COMMANDS_SET = {SQLTokenizer.TRANSACTION};
    private static final int[] COMMANDS_CREATE_UNIQUE = {SQLTokenizer.INDEX,
        SQLTokenizer.CLUSTERED, SQLTokenizer.NONCLUSTERED};
    private static final int[] MISSING_TABLE = {SQLTokenizer.TABLE};
    private static final int[] ESCAPE_MISSING_CLOSE = {SQLTokenizer.ESCAPE_R};
    private static final int[] MISSING_EXPRESSION = {SQLTokenizer.VALUE};
    private static final int[] MISSING_IDENTIFIER = {SQLTokenizer.IDENTIFIER};
    private static final int[] MISSING_BY = {SQLTokenizer.BY};
    private static final int[] MISSING_PARENTHESIS_L = {SQLTokenizer.PARENTHESIS_L};
    private static final int[] MISSING_PARENTHESIS_R = {SQLTokenizer.PARENTHESIS_R};
    private static final int[] MISSING_DATATYPE = {SQLTokenizer.BIT,
        SQLTokenizer.BOOLEAN, SQLTokenizer.BINARY, SQLTokenizer.VARBINARY,
        SQLTokenizer.RAW, SQLTokenizer.LONGVARBINARY, SQLTokenizer.BLOB,
        SQLTokenizer.TINYINT, SQLTokenizer.SMALLINT, SQLTokenizer.INT,
        SQLTokenizer.COUNTER, SQLTokenizer.BIGINT, SQLTokenizer.SMALLMONEY,
        SQLTokenizer.MONEY, SQLTokenizer.DECIMAL, SQLTokenizer.NUMERIC,
        SQLTokenizer.REAL, SQLTokenizer.FLOAT, SQLTokenizer.DOUBLE,
        SQLTokenizer.DATE, SQLTokenizer.TIME, SQLTokenizer.TIMESTAMP,
        SQLTokenizer.SMALLDATETIME, SQLTokenizer.CHAR, SQLTokenizer.NCHAR,
        SQLTokenizer.VARCHAR, SQLTokenizer.NVARCHAR, SQLTokenizer.LONG,
        SQLTokenizer.LONGNVARCHAR, SQLTokenizer.LONGVARCHAR,
        SQLTokenizer.CLOB, SQLTokenizer.NCLOB,
        SQLTokenizer.UNIQUEIDENTIFIER, SQLTokenizer.JAVA_OBJECT,
        SQLTokenizer.SYSNAME};
    private static final int[] MISSING_SQL_DATATYPE = {
        SQLTokenizer.SQL_BIGINT, SQLTokenizer.SQL_BINARY,
        SQLTokenizer.SQL_BIT, SQLTokenizer.SQL_CHAR, SQLTokenizer.SQL_DATE,
        SQLTokenizer.SQL_DECIMAL, SQLTokenizer.SQL_DOUBLE,
        SQLTokenizer.SQL_FLOAT, SQLTokenizer.SQL_INTEGER,
        SQLTokenizer.SQL_LONGVARBINARY, SQLTokenizer.SQL_LONGVARCHAR,
        SQLTokenizer.SQL_REAL, SQLTokenizer.SQL_SMALLINT,
        SQLTokenizer.SQL_TIME, SQLTokenizer.SQL_TIMESTAMP,
        SQLTokenizer.SQL_TINYINT, SQLTokenizer.SQL_VARBINARY,
        SQLTokenizer.SQL_VARCHAR};
    private static final int[] MISSING_INTO = {SQLTokenizer.INTO};
    private static final int[] MISSING_BETWEEN_IN = {SQLTokenizer.BETWEEN,
        SQLTokenizer.IN};
    private static final int[] MISSING_NOT_NULL = {SQLTokenizer.NOT,
        SQLTokenizer.NULL};
    private static final int[] MISSING_NULL = {SQLTokenizer.NULL};
    private static final int[] MISSING_COMMA = {SQLTokenizer.COMMA};
    private static final int[] MISSING_COMMA_PARENTHESIS = {
        SQLTokenizer.COMMA, SQLTokenizer.PARENTHESIS_R};
    private static final int[] MISSING_PARENTHESIS_VALUES_SELECT = {
        SQLTokenizer.PARENTHESIS_L, SQLTokenizer.VALUES,
        SQLTokenizer.SELECT};
    private static final int[] MISSING_TOKEN_LIST = {SQLTokenizer.COMMA,
        SQLTokenizer.FROM, SQLTokenizer.GROUP, SQLTokenizer.HAVING,
        SQLTokenizer.ORDER};
    private static final int[] MISSING_FROM = {SQLTokenizer.FROM};
    private static final int[] MISSING_SET = {SQLTokenizer.SET};
    private static final int[] MISSING_EQUALS = {SQLTokenizer.EQUALS};
    private static final int[] MISSING_WHERE = {SQLTokenizer.WHERE};
    private static final int[] MISSING_WHERE_COMMA = {SQLTokenizer.WHERE,
        SQLTokenizer.COMMA};
    private static final int[] MISSING_ISOLATION = {SQLTokenizer.ISOLATION};
    private static final int[] MISSING_LEVEL = {SQLTokenizer.LEVEL};
    private static final int[] COMMANDS_TRANS_LEVEL = {SQLTokenizer.READ,
        SQLTokenizer.REPEATABLE, SQLTokenizer.SERIALIZABLE};
    private static final int[] MISSING_READ = {SQLTokenizer.READ};
    private static final int[] MISSING_COMM_UNCOMM = {SQLTokenizer.COMMITTED,
        SQLTokenizer.UNCOMMITTED};
    private static final int[] MISSING_OPTIONS_DATATYPE = {
        SQLTokenizer.DEFAULT, SQLTokenizer.IDENTITY, SQLTokenizer.NOT,
        SQLTokenizer.NULL, SQLTokenizer.PRIMARY, SQLTokenizer.UNIQUE,
        SQLTokenizer.COMMA, SQLTokenizer.PARENTHESIS_R};
    private static final int[] MISSING_NUMBERVALUE = {SQLTokenizer.NUMBERVALUE};
    private static final int[] MISSING_AND = {SQLTokenizer.AND};
    private static final int[] MISSING_JOIN = {SQLTokenizer.JOIN};
    private static final int[] MISSING_OUTER_JOIN = {SQLTokenizer.OUTER,
        SQLTokenizer.JOIN};
    private static final int[] MISSING_OJ = {SQLTokenizer.OJ};
    private static final int[] MISSING_ON = {SQLTokenizer.ON};
    private static final int[] MISSING_KEYTYPE = {SQLTokenizer.PRIMARY,
        SQLTokenizer.UNIQUE, SQLTokenizer.FOREIGN};
    private static final int[] MISSING_KEY = {SQLTokenizer.KEY};
    private static final int[] MISSING_REFERENCES = {SQLTokenizer.REFERENCES};
    private static final int[] MISSING_AS = {SQLTokenizer.AS};
    private static final int[] MISSING_SELECT = {SQLTokenizer.SELECT};
    private static final int[] MISSING_INTERVALS = {
        SQLTokenizer.SQL_TSI_FRAC_SECOND, SQLTokenizer.SQL_TSI_SECOND,
        SQLTokenizer.SQL_TSI_MINUTE, SQLTokenizer.SQL_TSI_HOUR,
        SQLTokenizer.SQL_TSI_DAY, SQLTokenizer.SQL_TSI_WEEK,
        SQLTokenizer.SQL_TSI_MONTH, SQLTokenizer.SQL_TSI_QUARTER,
        SQLTokenizer.SQL_TSI_YEAR, SQLTokenizer.MILLISECOND,
        SQLTokenizer.SECOND, SQLTokenizer.MINUTE, SQLTokenizer.HOUR,
        SQLTokenizer.DAY, SQLTokenizer.WEEK, SQLTokenizer.MONTH,
        SQLTokenizer.QUARTER, SQLTokenizer.YEAR, SQLTokenizer.D};
    private static final int[] MISSING_ALL = {SQLTokenizer.ALL};
    private static final int[] MISSING_THEN = {SQLTokenizer.THEN};
    private static final int[] MISSING_WHEN_ELSE_END = {SQLTokenizer.WHEN,
        SQLTokenizer.ELSE, SQLTokenizer.END};
    private static final int[] MISSING_ADD_ALTER_DROP = {SQLTokenizer.ADD,
        SQLTokenizer.ALTER, SQLTokenizer.DROP};

}
